#include <iostream>
#include <string>
#include <math.h>
#include <sys/time.h>
#include <GL/glew.h>
#include <GL/glut.h>

#include "shader.h"

using namespace std;

GLuint g_program;
GLuint g_programCameraPositionLocation;
GLuint g_programLightPositionLocation;
GLuint g_programLightColorLocation;

#define NUM_LIGHTS 3
float g_cameraPosition[3];
float g_lightPosition[NUM_LIGHTS * 3];
float g_lightColor[NUM_LIGHTS * 3];
float g_lightRotation;

void changeSize(int w, int h) {

	// Prevent a divide by zero, when window is too short
	// (you cant make a window of zero width).
	if(h == 0)
		h = 1;

	float ratio = 1.0* w / h;

	// Reset the coordinate system before modifying
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	
	// Set the viewport to be the entire window
    glViewport(0, 0, w, h);

	// Set the correct perspective.
	gluPerspective(45,ratio,1,1000);
	glMatrixMode(GL_MODELVIEW);


}

	static unsigned int
getTicks(void)
{
	struct timeval t;

	gettimeofday(&t, NULL);

	return (t.tv_sec * 1000) + (t.tv_usec / 1000);
}

void
renderIdle(void)
{
	static unsigned int prevTicks = 0;
	unsigned int ticks;
	float secondsElapsed;
	int i;

	/* calculate the number of seconds that have
	 * passed since the last call to this function */
	if(prevTicks == 0)
		prevTicks = getTicks();
	ticks = getTicks();
	secondsElapsed = (float)(ticks - prevTicks) / 1000.0f;
	prevTicks = ticks;

	/* update the light positions */
	g_lightRotation += (M_PI / 4.0f) * secondsElapsed;
	for(i = 0; i < NUM_LIGHTS; ++i) {
		const float radius = 1.75f;
		float r = (((M_PI * 2.0f) / (float)NUM_LIGHTS) * (float)i) + g_lightRotation;

		g_lightPosition[i * 3 + 0] = cosf(r) * radius;
		g_lightPosition[i * 3 + 1] = cosf(r) * sinf(r);
		g_lightPosition[i * 3 + 2] = sinf(r) * radius;
	}

	glutPostRedisplay();
} 

void renderScene(void) {

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glLoadIdentity();
	gluLookAt(0.0,0.0,5.0, 
		      0.0,0.0,-1.0, 0.0f,1.0f,0.0f);

	for (int i = 0; i < NUM_LIGHTS; ++i) {
		glPushMatrix();
		glTranslatef(g_lightPosition[i * 3 + 0], g_lightPosition[i * 3 + 1], g_lightPosition[i * 3 + 2]); 
		glColor3fv(g_lightColor + (i * 3));
		glutSolidSphere(0.04f, 36, 36);
		glPopMatrix();
	}

	glUseProgram(g_program);
	glUniform3fv(g_programCameraPositionLocation, 1, g_cameraPosition);
	glUniform3fv(g_programLightPositionLocation, NUM_LIGHTS, g_lightPosition);
	glUniform3fv(g_programLightColorLocation, NUM_LIGHTS, g_lightColor);

	glutSolidSphere(1, 64, 64);

	glUseProgram(0);//禁止程序

	glutSwapBuffers();
}

void processNormalKeys(unsigned char key, int x, int y) {

	if (key == 27) 
		exit(0);
}

#define printOpenGLError() printOglError(__FILE__, __LINE__)

int printOglError(char *file, int line)
{
    //
    // Returns 1 if an OpenGL error occurred, 0 otherwise.
    //
    GLenum glErr;
    int    retCode = 0;

    glErr = glGetError();
    while (glErr != GL_NO_ERROR)
    {
		std::cerr << "glError in "
			<< "file " << file << " @ line " << line 
			<< ":" << gluErrorString(glErr) << std::endl;
        retCode = 1;
        glErr = glGetError();
    }
    return retCode;
}


void printInfoLog(GLhandleARB obj)
{
    int infologLength = 0;
    int charsWritten  = 0;
    char *infoLog;

	glGetObjectParameterivARB(obj, GL_OBJECT_INFO_LOG_LENGTH_ARB,
                                         &infologLength);

    if (infologLength > 0)
    {
        infoLog = (char *)malloc(infologLength);
        glGetInfoLogARB(obj, infologLength, &charsWritten, infoLog);
		std::cout << infoLog << std::endl;
        free(infoLog);
    }
}



void setShaders() {
	GLint result;

	//创建对像并添加shader
	g_program = glCreateProgram();
	shaderAttachFromFile(g_program, GL_VERTEX_SHADER, "shader.vert");
	shaderAttachFromFile(g_program, GL_FRAGMENT_SHADER, "shader.frag");

	glLinkProgram(g_program);
	glGetProgramiv(g_program, GL_LINK_STATUS, &result);
	if (result == GL_FALSE) {
		GLint length;
		glGetShaderiv(g_program, GL_INFO_LOG_LENGTH, &length);
		std::string log(length, ' ');
		glGetShaderInfoLog(g_program, length, &length, &log[0]);
		std::cerr << "shader链接失败,原因：" << log << endl;
		return;
	}
	//得到uniform 位置
	g_programCameraPositionLocation = glGetUniformLocation(g_program, "cameraPosition");
	g_programLightPositionLocation = glGetUniformLocation(g_program, "lightPosition");
	g_programLightColorLocation = glGetUniformLocation(g_program, "lightColor");

	g_lightPosition[0] = 0.0f; g_lightPosition[1] = 0.0f; g_lightPosition[2] = 2.0f; 
	g_lightPosition[3] = 2.0f * cos(45.0f); g_lightPosition[4] = 0.0f; g_lightPosition[5] = 2.0f * sin(45.0f); 
	g_lightPosition[6] = 2.0f * cos(135.0f); g_lightPosition[7] = 0.0f; g_lightPosition[8] = 2.0f * sin(135.0f); 

	g_lightColor[0] = 1.0f; g_lightColor[1] = 0.0f; g_lightColor[2] = 0.0f;
	g_lightColor[3] = 0.0f; g_lightColor[4] = 1.0f; g_lightColor[5] = 0.0f;
	g_lightColor[6] = 0.0f; g_lightColor[7] = 0.0f; g_lightColor[8] = 1.0f;

	g_lightRotation = 0.0f;

	g_cameraPosition[0] = 0.0f; g_cameraPosition[1] = 0.0f; g_cameraPosition[2] = 4.0f; 
}




int main(int argc, char **argv) {
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
	glutInitWindowPosition(100,100);
	glutInitWindowSize(640, 480);
	glutCreateWindow("OpenGL Shader");

	glutDisplayFunc(renderScene);
	glutIdleFunc(renderIdle);
	glutReshapeFunc(changeSize);
	glutKeyboardFunc(processNormalKeys);

	glEnable(GL_DEPTH_TEST);
	glClearColor(0.0, 0.0, 0.0, 0.0);
	glEnable(GL_CULL_FACE);

	glewInit();
	if (GLEW_ARB_vertex_shader && GLEW_ARB_fragment_shader)
		std::cout << "Ready for GLSL." << std::endl;
	else {
		std::cout << "No GLSL support." << std::endl;
		exit(1);
	}

	setShaders();

	glutMainLoop();

	return 0;
}

